﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace OCRTheNewHope
{
    public static class Synthesizer
    {
        public static double[,] Generate2DRotationMatrix(double degree)
        {
            double[,] rVal = new double[2, 2];

            double radian = degree.ToRadian();

            rVal[0, 0] = Math.Cos(radian);
            rVal[0, 1] = -1 * Math.Sin(radian);
            rVal[1, 0] = Math.Sin(radian);
            rVal[1, 1] = Math.Cos(radian);

            return rVal;
        }

        static Digit Rotate(Digit digit, double degree)
        {
            Digit rVal = null;

            byte[,] rotatedMatrix = new byte[digit.OriginalGrayscaleMatrix.GetLength(0), digit.OriginalGrayscaleMatrix.GetLength(1)];

            double[,] rotationMatrix = Generate2DRotationMatrix(degree);
            double[,] rotationMatrixInverse = rotationMatrix.Inverse2DMatrix();

            int offset = digit.OriginalGrayscaleMatrix.GetLength(0) / 2;

            for (int i = 0; i < digit.OriginalGrayscaleMatrix.GetLength(0); i++)
            {
                for (int j = 0; j < digit.OriginalGrayscaleMatrix.GetLength(1); j++)
                {
                    int y = -offset + i;
                    int x = j - offset;

                    double[] v = new double[] { x, y };

                    double[] result = v.Multiply(rotationMatrixInverse);

                    double calculatedY = result[1] + offset;
                    double calculatedX = result[0] + offset;

                    rotatedMatrix[i, j] = Interpolate(digit.OriginalGrayscaleMatrix, calculatedX, calculatedY);
                }
            }

            rVal = new Digit(digit.DigitID, digit.DigitValue, rotatedMatrix);
            return rVal;

        }

        static byte Interpolate(byte[,] matrix, double x, double y) // check if it's in the matrix
        {
            byte rVal = 0;

            double xDiff;
            double yDiff;

            int[] leftUpper = new int[2];

            leftUpper[0] = (int)x;
            leftUpper[1] = (int)y;

            xDiff = Math.Abs(x - leftUpper[0]);
            yDiff = Math.Abs(y - leftUpper[1]);

            // Check if the rotated position lies in the boundaries 

            if (leftUpper[0] < 27 && leftUpper[1] < 27 && leftUpper[0] >= 0 && leftUpper[1] >= 0)
            {
                // Calculate bilinear interpolation coefficients 

                double[] coeffs = new double[4];

                coeffs[0] = (1 - xDiff) * (1 - yDiff);
                coeffs[1] = (1 - yDiff) * xDiff;
                coeffs[2] = xDiff * yDiff;
                coeffs[3] = (1 - xDiff) * yDiff;

                rVal = (byte)(coeffs[0] * matrix[leftUpper[1], leftUpper[0]] + coeffs[1] * matrix[leftUpper[1], leftUpper[0] + 1] + coeffs[2] * matrix[leftUpper[1] + 1, leftUpper[0] + 1] + coeffs[3] * matrix[leftUpper[1] + 1, leftUpper[0]]);
            }
            else if ((leftUpper[1] == 27 && leftUpper[0] == 27))
            {
                rVal = (byte)(matrix[leftUpper[1], leftUpper[0]]);
            }
            else if ((leftUpper[0] == 27 && leftUpper[1] >= 0 && leftUpper[1] < 27))
            {
                rVal = (byte)(matrix[leftUpper[1], leftUpper[0]] * xDiff + matrix[leftUpper[1] + 1, leftUpper[0]] * (1 - xDiff));
            }
            else if ((leftUpper[1] == 27 && leftUpper[0] >= 0 && leftUpper[0] < 27))
            {
                rVal = (byte)(matrix[leftUpper[1], leftUpper[0]] * yDiff + matrix[leftUpper[1], leftUpper[0] + 1] * (1 - yDiff));
            }

            return rVal;
        }

        static Digit AffineTransformation(int digitID, Digit digit, double affineDegree = 15, double rotationDegree = 7, double[,] affineMatrix = null)
        {
            if (affineMatrix == null)
                affineMatrix = new double[2, 2] { { 1.05, 0 }, { 0, 0.95 } };

            Digit returnDigit = null;

            byte[,] transformedMatrix = new byte[digit.OriginalGrayscaleMatrix.GetLength(0), digit.OriginalGrayscaleMatrix.GetLength(1)];

            double[,] affineTransformedPixelCoords = new double[2, 1];

            int offset = digit.OriginalGrayscaleMatrix.GetLength(0) / 2;

            for (int i = 0; i < digit.OriginalGrayscaleMatrix.GetLength(0); i++)
            {
                for (int j = 0; j < digit.OriginalGrayscaleMatrix.GetLength(1); j++)
                {

                    int y = -offset + i;
                    int x = j - offset;

                    affineTransformedPixelCoords = Extensions.TransformationMatrix2D(new double[,] { { x }, { y } }, affineDegree, rotationDegree, affineMatrix);

                    double calculatedY = affineTransformedPixelCoords[1, 0] + offset; // Check this out.  - result[1] + offset; 
                    double calculatedX = affineTransformedPixelCoords[0, 0] + offset;

                    transformedMatrix[i, j] = Interpolate(digit.OriginalGrayscaleMatrix, calculatedX, calculatedY);

                }
            }

            returnDigit = new Digit(digitID, digit.DigitValue, transformedMatrix);

            return returnDigit;
        }

        public static void Synthesize(Digit[] digits)
        {
            BinaryWriter b = FileOperations.CreateHeader("C:\\last.data", 60000 * 13, 28, 28);

            FileOperations.WriteDatabase(b, digits);

            Digit[] rVal = new Digit[digits.Length];

            int counter = 60000;
            int index = 0;

            for (int i = -7; i <= 7; i += 7)
            {
                for (int j = -15; j <= 15; j += 30)
                {
                    for (int k = 0; k < 2; k++)
                    {
                        double[,] affineMatrix = new double[2, 2];
                        if (k == 0)
                            affineMatrix = new double[2, 2] { { 0.95, 0 }, { 0, 1.05 } };
                        else
                            affineMatrix = new double[2, 2] { { 1.05, 0 }, { 0, 0.95 } };

                        for (int p = 0; p < digits.Length; p++)
                        {
                            rVal[index] = AffineTransformation(counter, digits[p], j, i, affineMatrix);
                            counter++;
                            index++;

                            if (counter % 1000 == 0)
                            {
                                Console.WriteLine(counter);
                            }
                        }

                        index = 0;
                        FileOperations.WriteDatabase(b, rVal);
                    }
                }
            }
        }
    }
}
